﻿//************************************************************************************************************
// CoNatural.Data.dll
// Author: Roger Torres
//************************************************************************************************************
// You can use, modify, and distribute the source code and executable programs based on the source code.
// This source code is provided "as is" and without warranties as to performance or merchantability. 
// The author and/or distributors of this source code may have made statements about this source code. 
// Any such statements do not constitute warranties and shall not be relied on by the user in deciding 
// whether to use this source code. This source code is provided without any express or implied warranties 
// whatsoever. Because of the diversity of conditions and hardware under which this source code may be used, 
// no warranty of fitness for a particular purpose is offered. The user is advised to test the source code 
// thoroughly before relying on it. The user must assume the entire risk of using the source code.
//************************************************************************************************************

using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Text;

namespace CoNatural.Data.Helpers { 
	/// <summary>
	/// Helps to create CLR properties from data types
	/// </summary>
	public class PropertyHelper {
		public string Name { get; private set; }
		public string TypeName { get; private set; }
		public string Attribute { get; private set; }
		public int Size { get; private set; }

		/// <summary>
		/// Creates property from DataColumn
		/// </summary>
		/// <param name="dataColumn">The DataColumn.</param>
		public PropertyHelper(DataColumn dataColumn) {
			Name = dataColumn.ColumnName.Replace(" ", "_");
			TypeName = dataColumn.DataType.Name;
			Attribute = string.Empty;
		}

		/// <summary>
		/// Creates property from SqlParameter
		/// </summary>
		/// <param name="parameter">The SqlParameter.</param>
		public PropertyHelper(SqlParameter parameter) {
			// map SqlDbType to DbType (to be used later to map CLR type)
			switch (parameter.SqlDbType) {
				// handle string types
				case SqlDbType.NChar:
				case SqlDbType.NText:
				case SqlDbType.NVarChar:
				case SqlDbType.Text:
				case SqlDbType.VarChar:
					parameter.DbType = DbType.String;
					break;

				// handle binary types
				case SqlDbType.Binary:
				case SqlDbType.Image:
				case SqlDbType.VarBinary:
					parameter.DbType = DbType.Binary;
					break;

				// handle numeric types
				case SqlDbType.SmallMoney:
				case SqlDbType.Money:
				case SqlDbType.Decimal:
					parameter.DbType = DbType.Decimal;
					break;

				// handler variant type
				case SqlDbType.Variant:
					parameter.DbType = DbType.Object;
					break;
			}

			Name = parameter.ParameterName;
			TypeName = parameter.DbType.ToString() + (parameter.IsNullable ? "?" : string.Empty);
			Attribute = string.Empty;

			Initialize(parameter, parameter.Precision, parameter.Scale, parameter.UdtTypeName, parameter.IsNullable);
		}

		/// <summary>
		/// Creates property from DbParameter
		/// </summary>
		/// <param name="parameter">The DbParameter.</param>
		/// <param name="precision">Parameter numeric precision (decimals).</param>
		/// <param name="scale">Parameter numeric scale (decimals).</param>
		public PropertyHelper(DbParameter parameter, int precision, int scale) {
			Name = parameter.ParameterName;
			TypeName = parameter.DbType.ToString() + (parameter.IsNullable ? "?" : string.Empty);
			Attribute = string.Empty;

			Initialize(parameter, precision, scale, string.Empty, parameter.IsNullable);
		}

		private void Initialize(DbParameter parameter, int precision, int scale, string udtTypeName, bool isNullable) {
			switch (parameter.DbType) {
				case DbType.String:
				case DbType.StringFixedLength:
				case DbType.AnsiString:
				case DbType.AnsiStringFixedLength:
				case DbType.Xml: // this could be handled as XmlDocument in future releases
					TypeName = "string"; // all strings are .NET string types (this line removes the ? in the type when is nullable)
					if (parameter.Direction != ParameterDirection.Input || parameter.Size > 0 || isNullable)
						Attribute = CreateParameterAttribute(parameter.Direction, parameter.Size, -1, -1, isNullable);
					break;

				case DbType.Object:
					// handle new SQL Server 2008 types
					// but user must add reference to assembly Microsoft.SqlServer.Types
					switch(udtTypeName) { 
						case "HierarchyId":
							TypeName = "Microsoft.SqlServer.Types.SqlHierarchyId";
							break;

						case "Geography":
							TypeName = "Microsoft.SqlServer.Types.SqlGeography";
							break;

						case "Geometry":
							TypeName = "Microsoft.SqlServer.Types.SqlGeometry";
							break;

						default:
							TypeName = "object";
							break;
					}
					if (parameter.Direction != ParameterDirection.Input)
						Attribute = CreateParameterAttribute(parameter.Direction, -1, -1, -1, false);
					break;

				case DbType.Decimal:
				case DbType.Currency:
				case DbType.VarNumeric:
					TypeName = "Decimal" + (parameter.IsNullable ? "?" : string.Empty);
					Attribute = CreateParameterAttribute(parameter.Direction, -1, precision, scale, false);
					break;

				case DbType.Date:
				case DbType.Time:
				case DbType.DateTime2:
					TypeName = "DateTime" + (parameter.IsNullable ? "?" : string.Empty);
					if (parameter.Direction != ParameterDirection.Input)
						Attribute = CreateParameterAttribute(parameter.Direction, -1, -1, -1, false);
					break;

				case DbType.DateTimeOffset:
					TypeName = "DateTimeOffset" + (parameter.IsNullable ? "?" : string.Empty);
					if (parameter.Direction != ParameterDirection.Input)
						Attribute = CreateParameterAttribute(parameter.Direction, -1, -1, -1, false);
					break;

				case DbType.Binary:
					TypeName = "byte[]";
					if (parameter.Direction != ParameterDirection.Input || parameter.Size > 0)
						Attribute = CreateParameterAttribute(parameter.Direction, parameter.Size, -1, -1, false);
					break;

				default:
					if (parameter.Direction != ParameterDirection.Input)
						Attribute = CreateParameterAttribute(parameter.Direction, -1, -1, -1, false);
					break;
			}
		}

		private string CreateParameterAttribute(ParameterDirection direction, int size, int precision, int scale, bool isNullableString) {
			Size = size;
			var sb = new StringBuilder();
			sb.Append("[Parameter(");
			if (direction != ParameterDirection.Input) {
				sb.Append("ParameterDirection.").Append(direction);
				if (size > 0 || precision >= 0 || scale >= 0 || isNullableString)
					sb.Append(", ");
			}
			if(isNullableString)
				sb.Append(size).Append(", true");
			else {
				if(size > 0)
					sb.Append(size);
				else if(precision >= 0 || scale >= 0)
					sb.Append(precision).Append(", ").Append(scale);
			}
			sb.Append(")] ");
			return sb.ToString();
		}
	}
}
